/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.WeakHashMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.ZkCoreNodeProps;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStreamBase;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.ContentStreamHandlerBase;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryRequestBase;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.response.BinaryQueryResponseWriter;
import org.apache.solr.response.QueryResponseWriter;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.servlet.cache.HttpCacheHeaderUtil;
import org.apache.solr.servlet.cache.Method;
import org.apache.solr.util.FastWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This filter looks at the incoming URL maps them to handlers defined in solrconfig.xml
 * 
 * @since solr 1.2
 */
public class SolrDispatchFilter implements Filter {
	final Logger										log;
	
	protected volatile CoreContainer					cores;
	
	protected String									pathPrefix			= null;												// strip this from the beginning of a path
	protected String									abortErrorMessage	= null;
	protected final Map<SolrConfig, SolrRequestParsers>	parsers				= new WeakHashMap<SolrConfig, SolrRequestParsers>();
	
	private static final Charset						UTF8				= Charset.forName("UTF-8");
	
	public SolrDispatchFilter() {
		try {
			log = LoggerFactory.getLogger(SolrDispatchFilter.class);
		}
		catch (NoClassDefFoundError e) {
			throw new SolrException(ErrorCode.SERVER_ERROR, "Could not find necessary SLF4j logging jars. If using Jetty, the SLF4j logging jars need to go in " + "the jetty lib/ext directory. For other containers, the corresponding directory should be used. "
					+ "For more information, see: http://wiki.apache.org/solr/SolrLogging", e);
		}
	}
	
	@Override
	public void init(FilterConfig config) throws ServletException {
		log.info("SolrDispatchFilter.init()");
		
		CoreContainer.Initializer init = createInitializer();
		try {
			// web.xml configuration
			this.pathPrefix = config.getInitParameter("path-prefix");
			
			this.cores = init.initialize();
			log.info("user.dir=" + System.getProperty("user.dir"));
		}
		catch (Throwable t) {
			// catch this so our filter still works
			log.error("Could not start Solr. Check solr/home property and the logs");
			SolrCore.log(t);
		}
		
		log.info("SolrDispatchFilter.init() done");
	}
	
	public CoreContainer getCores() {
		return cores;
	}
	
	/** Method to override to change how CoreContainer initialization is performed. */
	protected CoreContainer.Initializer createInitializer() {
		return new CoreContainer.Initializer();
	}
	
	@Override
	public void destroy() {
		if (cores != null) {
			cores.shutdown();
			cores = null;
		}
	}
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		doFilter(request, response, chain, false);
	}
	
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain, boolean retry) throws IOException, ServletException {
		if (abortErrorMessage != null) {
			((HttpServletResponse) response).sendError(500, abortErrorMessage);
			return;
		}
		
		if (this.cores == null) {
			((HttpServletResponse) response).sendError(503, "Server is shutting down");
			return;
		}
		CoreContainer cores = this.cores;
		SolrCore core = null;
		SolrQueryRequest solrReq = null;
		Aliases aliases = null;
		
		if (request instanceof HttpServletRequest) {
			HttpServletRequest req = (HttpServletRequest) request;
			HttpServletResponse resp = (HttpServletResponse) response;
			SolrRequestHandler handler = null;
			String corename = "";
			String origCorename = null;
			try {
				// put the core container in request attribute
				req.setAttribute("org.apache.solr.CoreContainer", cores);
				String path = req.getServletPath();
				if (req.getPathInfo() != null) {
					// this lets you handle /update/commit when /update is a servlet
					path += req.getPathInfo();
				}
				if (pathPrefix != null && path.startsWith(pathPrefix)) {
					path = path.substring(pathPrefix.length());
				}
				// check for management path
				String alternate = cores.getManagementPath();
				if (alternate != null && path.startsWith(alternate)) {
					path = path.substring(0, alternate.length());
				}
				// unused feature ?
				int idx = path.indexOf(':');
				if (idx > 0) {
					// save the portion after the ':' for a 'handler' path parameter
					path = path.substring(0, idx);
				}
				
				// Check for the core admin page
				if (path.equals(cores.getAdminPath())) {
					handler = cores.getMultiCoreHandler();
					solrReq = SolrRequestParsers.DEFAULT.parse(null, path, req);
					handleAdminRequest(req, response, handler, solrReq);
					return;
				}
				boolean usingAliases = false;
				List<String> collectionsList = null;
				// Check for the core admin collections url
				if (path.equals("/admin/collections")) {
					handler = cores.getCollectionsHandler();
					solrReq = SolrRequestParsers.DEFAULT.parse(null, path, req);
					handleAdminRequest(req, response, handler, solrReq);
					return;
				}
				else {
					// otherwise, we should find a core from the path
					idx = path.indexOf("/", 1);
					if (idx > 1) {
						// try to get the corename as a request parameter first
						corename = path.substring(1, idx);
						
						// look at aliases
						if (cores.isZooKeeperAware()) {
							origCorename = corename;
							ZkStateReader reader = cores.getZkController().getZkStateReader();
							aliases = reader.getAliases();
							if (aliases != null && aliases.collectionAliasSize() > 0) {
								usingAliases = true;
								String alias = aliases.getCollectionAlias(corename);
								if (alias != null) {
									collectionsList = StrUtils.splitSmart(alias, ",", true);
									corename = collectionsList.get(0);
								}
							}
						}
						
						core = cores.getCore(corename);
						
						if (core != null) {
							path = path.substring(idx);
						}
					}
					if (core == null) {
						if (!cores.isZooKeeperAware()) {
							core = cores.getCore("");
						}
					}
				}
				
				if (core == null && cores.isZooKeeperAware()) {
					// we couldn't find the core - lets make sure a collection was not specified instead
					core = getCoreByCollection(cores, corename, path);
					
					if (core != null) {
						// we found a core, update the path
						path = path.substring(idx);
					}
					
					// if we couldn't find it locally, look on other nodes
					if (core == null && idx > 0) {
						String coreUrl = getRemotCoreUrl(cores, corename, origCorename);
						if (coreUrl != null) {
							path = path.substring(idx);
							remoteQuery(coreUrl + path, req, solrReq, resp);
							return;
						}
						else {
							if (!retry) {
								// we couldn't find a core to work with, try reloading aliases
								// TODO: it would be nice if admin ui elements skipped this...
								ZkStateReader reader = cores.getZkController().getZkStateReader();
								reader.updateAliases();
								doFilter(request, response, chain, true);
								return;
							}
						}
					}
					
					// try the default core
					if (core == null) {
						core = cores.getCore("");
					}
				}
				
				// With a valid core...
				if (core != null) {
					final SolrConfig config = core.getSolrConfig();
					// get or create/cache the parser for the core
					SolrRequestParsers parser = null;
					parser = parsers.get(config);
					if (parser == null) {
						parser = new SolrRequestParsers(config);
						parsers.put(config, parser);
					}
					
					// Handle /schema/* paths via Restlet
					if (path.startsWith("/schema")) {
						solrReq = parser.parse(core, path, req);
						SolrRequestInfo.setRequestInfo(new SolrRequestInfo(solrReq, new SolrQueryResponse()));
						if (path.equals(req.getServletPath())) {
							// avoid endless loop - pass through to Restlet via webapp
							chain.doFilter(request, response);
						}
						else {
							// forward rewritten URI (without path prefix and core/collection name) to Restlet
							req.getRequestDispatcher(path).forward(request, response);
						}
						return;
					}
					
					// Determine the handler from the url path if not set
					// (we might already have selected the cores handler)
					if (handler == null && path.length() > 1) { // don't match "" or "/" as valid path
						handler = core.getRequestHandler(path);
						// no handler yet but allowed to handle select; let's check
						if (handler == null && parser.isHandleSelect()) {
							if ("/select".equals(path) || "/select/".equals(path)) {
								solrReq = parser.parse(core, path, req);
								String qt = solrReq.getParams().get(CommonParams.QT);
								handler = core.getRequestHandler(qt);
								if (handler == null) {
									throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "unknown handler: " + qt);
								}
								if (qt != null && qt.startsWith("/") && (handler instanceof ContentStreamHandlerBase)) {
									// For security reasons it's a bad idea to allow a leading '/', ex: /select?qt=/update see SOLR-3161
									// There was no restriction from Solr 1.4 thru 3.5 and it's not supported for update handlers.
									throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid Request Handler ('qt').  Do not use /select to access: " + qt);
								}
							}
							// ++++++huisou+++++++
							else if (handler == null && path.indexOf(".html") > 0) {
								// 扩展部分，针对本系统
								handler = core.getRequestHandler("standard");
							}
							// ++++++huisou+++++++
						}
					}
					
					// With a valid handler and a valid core...
					if (handler != null) {
						// if not a /select, create the request
						if (solrReq == null) {
							solrReq = parser.parse(core, path, req);
						}
						
						if (usingAliases) {
							processAliases(solrReq, aliases, collectionsList);
						}
						
						final Method reqMethod = Method.getMethod(req.getMethod());
						HttpCacheHeaderUtil.setCacheControlHeader(config, resp, reqMethod);
						// unless we have been explicitly told not to, do cache validation
						// if we fail cache validation, execute the query
						if (config.getHttpCachingConfig().isNever304() || !HttpCacheHeaderUtil.doCacheHeaderValidation(solrReq, req, reqMethod, resp)) {
							SolrQueryResponse solrRsp = new SolrQueryResponse();
							/*
							 * even for HEAD requests, we need to execute the handler to ensure we don't get an error (and to make sure the correct QueryResponseWriter is selected and we get the correct Content-Type)
							 */
							SolrRequestInfo.setRequestInfo(new SolrRequestInfo(solrReq, solrRsp));
							this.execute(req, handler, solrReq, solrRsp);
							HttpCacheHeaderUtil.checkHttpCachingVeto(solrRsp, resp, reqMethod);
							// add info to http headers
							// TODO: See SOLR-232 and SOLR-267.
							/*
							 * try { NamedList solrRspHeader = solrRsp.getResponseHeader(); for (int i=0; i<solrRspHeader.size(); i++) { ((javax.servlet.http.HttpServletResponse) response).addHeader(("Solr-" + solrRspHeader.getName(i)), String.valueOf(solrRspHeader.getVal(i))); } } catch
							 * (ClassCastException cce) { log.log(Level.WARNING, "exception adding response header log information", cce); }
							 */
							QueryResponseWriter responseWriter = core.getQueryResponseWriter(solrReq);
							writeResponse(solrRsp, response, responseWriter, solrReq, reqMethod);
						}
						return; // we are done with a valid handler
					}
				}
				log.debug("no handler or core retrieved for " + path + ", follow through...");
			}
			catch (Throwable ex) {
				sendError(core, solrReq, request, (HttpServletResponse) response, ex);
				return;
			}
			finally {
				if (solrReq != null) {
					log.debug("Closing out SolrRequest: {}", solrReq);
					solrReq.close();
				}
				if (core != null) {
					core.close();
				}
				SolrRequestInfo.clearRequestInfo();
			}
		}
		
		// Otherwise let the webapp handle the request
		chain.doFilter(request, response);
	}
	
	private void processAliases(SolrQueryRequest solrReq, Aliases aliases, List<String> collectionsList) {
		String collection = solrReq.getParams().get("collection");
		if (collection != null) {
			collectionsList = StrUtils.splitSmart(collection, ",", true);
		}
		if (collectionsList != null) {
			Set<String> newCollectionsList = new HashSet<String>(collectionsList.size());
			for (String col : collectionsList) {
				String al = aliases.getCollectionAlias(col);
				if (al != null) {
					List<String> aliasList = StrUtils.splitSmart(al, ",", true);
					newCollectionsList.addAll(aliasList);
				}
				else {
					newCollectionsList.add(col);
				}
			}
			if (newCollectionsList.size() > 0) {
				StringBuilder collectionString = new StringBuilder();
				Iterator<String> it = newCollectionsList.iterator();
				int sz = newCollectionsList.size();
				for (int i = 0; i < sz; i++) {
					collectionString.append(it.next());
					if (i < newCollectionsList.size() - 1) {
						collectionString.append(",");
					}
				}
				ModifiableSolrParams params = new ModifiableSolrParams(solrReq.getParams());
				params.set("collection", collectionString.toString());
				solrReq.setParams(params);
			}
		}
	}
	
	private void remoteQuery(String coreUrl, HttpServletRequest req, SolrQueryRequest solrReq, HttpServletResponse resp) throws IOException {
		try {
			String urlstr = coreUrl;
			
			String queryString = req.getQueryString();
			
			urlstr += queryString == null ? "" : "?" + queryString;
			
			URL url = new URL(urlstr);
			HttpURLConnection con = (HttpURLConnection) url.openConnection();
			con.setRequestMethod(req.getMethod());
			con.setUseCaches(false);
			
			con.setDoOutput(true);
			con.setDoInput(true);
			for (Enumeration e = req.getHeaderNames(); e.hasMoreElements();) {
				String headerName = e.nextElement().toString();
				con.setRequestProperty(headerName, req.getHeader(headerName));
			}
			try {
				con.connect();
				
				InputStream is;
				OutputStream os;
				if ("POST".equals(req.getMethod())) {
					is = req.getInputStream();
					os = con.getOutputStream(); // side effect: method is switched to POST
					try {
						IOUtils.copyLarge(is, os);
						os.flush();
					}
					finally {
						IOUtils.closeQuietly(os);
						IOUtils.closeQuietly(is); // TODO: I thought we weren't supposed to explicitly close servlet streams
					}
				}
				
				resp.setStatus(con.getResponseCode());
				
				for (Iterator i = con.getHeaderFields().entrySet().iterator(); i.hasNext();) {
					Map.Entry mapEntry = (Map.Entry) i.next();
					if (mapEntry.getKey() != null)
						resp.setHeader(mapEntry.getKey().toString(), ((List) mapEntry.getValue()).get(0).toString());
				}
				
				resp.setCharacterEncoding(con.getContentEncoding());
				resp.setContentType(con.getContentType());
				
				is = con.getInputStream();
				os = resp.getOutputStream();
				try {
					IOUtils.copyLarge(is, os);
					os.flush();
				}
				finally {
					IOUtils.closeQuietly(os); // TODO: I thought we weren't supposed to explicitly close servlet streams
					IOUtils.closeQuietly(is);
				}
			}
			finally {
				con.disconnect();
			}
		}
		catch (IOException e) {
			sendError(null, solrReq, req, resp, new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error trying to proxy request for url: " + coreUrl, e));
		}
		
	}
	
	private String getRemotCoreUrl(CoreContainer cores, String collectionName, String origCorename) {
		ClusterState clusterState = cores.getZkController().getClusterState();
		Collection<Slice> slices = clusterState.getActiveSlices(collectionName);
		boolean byCoreName = false;
		if (slices == null) {
			// look by core name
			byCoreName = true;
			Set<String> collections = clusterState.getCollections();
			for (String collection : collections) {
				slices = new ArrayList<Slice>();
				slices.addAll(clusterState.getActiveSlices(collection));
			}
		}
		
		if (slices == null || slices.size() == 0) {
			return null;
		}
		
		Set<String> liveNodes = clusterState.getLiveNodes();
		Iterator<Slice> it = slices.iterator();
		while (it.hasNext()) {
			Slice slice = it.next();
			Map<String, Replica> sliceShards = slice.getReplicasMap();
			for (ZkNodeProps nodeProps : sliceShards.values()) {
				ZkCoreNodeProps coreNodeProps = new ZkCoreNodeProps(nodeProps);
				if (liveNodes.contains(coreNodeProps.getNodeName()) && coreNodeProps.getState().equals(ZkStateReader.ACTIVE)) {
					if (byCoreName && !collectionName.equals(coreNodeProps.getCoreName())) {
						// if it's by core name, make sure they match
						continue;
					}
					if (coreNodeProps.getBaseUrl().equals(cores.getZkController().getBaseUrl())) {
						// don't count a local core
						continue;
					}
					String coreUrl;
					if (origCorename != null) {
						coreUrl = coreNodeProps.getBaseUrl() + "/" + origCorename;
					}
					else {
						coreUrl = coreNodeProps.getCoreUrl();
						if (coreUrl.endsWith("/")) {
							coreUrl = coreUrl.substring(0, coreUrl.length() - 1);
						}
					}
					
					return coreUrl;
				}
			}
		}
		return null;
	}
	
	private SolrCore getCoreByCollection(CoreContainer cores, String corename, String path) {
		String collection = corename;
		ZkStateReader zkStateReader = cores.getZkController().getZkStateReader();
		
		ClusterState clusterState = zkStateReader.getClusterState();
		Map<String, Slice> slices = clusterState.getActiveSlicesMap(collection);
		if (slices == null) {
			return null;
		}
		// look for a core on this node
		Set<Entry<String, Slice>> entries = slices.entrySet();
		SolrCore core = null;
		done: for (Entry<String, Slice> entry : entries) {
			// first see if we have the leader
			ZkNodeProps leaderProps = clusterState.getLeader(collection, entry.getKey());
			if (leaderProps != null) {
				core = checkProps(cores, path, leaderProps);
			}
			if (core != null) {
				break done;
			}
			
			// check everyone then
			Map<String, Replica> shards = entry.getValue().getReplicasMap();
			Set<Entry<String, Replica>> shardEntries = shards.entrySet();
			for (Entry<String, Replica> shardEntry : shardEntries) {
				Replica zkProps = shardEntry.getValue();
				core = checkProps(cores, path, zkProps);
				if (core != null) {
					break done;
				}
			}
		}
		return core;
	}
	
	private SolrCore checkProps(CoreContainer cores, String path, ZkNodeProps zkProps) {
		String corename;
		SolrCore core = null;
		if (cores.getZkController().getNodeName().equals(zkProps.getStr(ZkStateReader.NODE_NAME_PROP))) {
			corename = zkProps.getStr(ZkStateReader.CORE_NAME_PROP);
			core = cores.getCore(corename);
		}
		return core;
	}
	
	private void handleAdminRequest(HttpServletRequest req, ServletResponse response, SolrRequestHandler handler, SolrQueryRequest solrReq) throws IOException {
		SolrQueryResponse solrResp = new SolrQueryResponse();
		SolrCore.preDecorateResponse(solrReq, solrResp);
		handler.handleRequest(solrReq, solrResp);
		SolrCore.postDecorateResponse(handler, solrReq, solrResp);
		if (log.isInfoEnabled() && solrResp.getToLog().size() > 0) {
			log.info(solrResp.getToLogAsString("[admin] "));
		}
		QueryResponseWriter respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get(solrReq.getParams().get(CommonParams.WT));
		if (respWriter == null)
			respWriter = SolrCore.DEFAULT_RESPONSE_WRITERS.get("standard");
		writeResponse(solrResp, response, respWriter, solrReq, Method.getMethod(req.getMethod()));
	}
	
	private void writeResponse(SolrQueryResponse solrRsp, ServletResponse response, QueryResponseWriter responseWriter, SolrQueryRequest solrReq, Method reqMethod) throws IOException {
		
		// Now write it out
		final String ct = responseWriter.getContentType(solrReq, solrRsp);
		// don't call setContentType on null
		if (null != ct)
			response.setContentType(ct);
		
		if (solrRsp.getException() != null) {
			NamedList info = new SimpleOrderedMap();
			int code = ResponseUtils.getErrorInfo(solrRsp.getException(), info, log);
			solrRsp.add("error", info);
			((HttpServletResponse) response).setStatus(code);
		}
		
		if (Method.HEAD != reqMethod) {
			if (responseWriter instanceof BinaryQueryResponseWriter) {
				BinaryQueryResponseWriter binWriter = (BinaryQueryResponseWriter) responseWriter;
				binWriter.write(response.getOutputStream(), solrReq, solrRsp);
			}
			else {
				// ++++++huisou+++++++
				try {
					String charset = ContentStreamBase.getCharsetFromContentType(ct);
					Writer out = (charset == null || charset.equalsIgnoreCase("UTF-8")) ? new OutputStreamWriter(response.getOutputStream(), UTF8) : new OutputStreamWriter(response.getOutputStream(), charset);
					out = new FastWriter(out);
					responseWriter.write(out, solrReq, solrRsp);
					out.flush();
				}
				catch (Exception ex) {
					log.info("*****************" + ex.getCause() + "+++++++++++++");
				}
				// ++++++huisou+++++++
			}
		}
		// else http HEAD request, nothing to write out, waited this long just to get ContentType
	}
	
	protected void execute(HttpServletRequest req, SolrRequestHandler handler, SolrQueryRequest sreq, SolrQueryResponse rsp) {
		// a custom filter could add more stuff to the request before passing it on.
		// for example: sreq.getContext().put( "HttpServletRequest", req );
		// used for logging query stats in SolrCore.execute()
		sreq.getContext().put("webapp", req.getContextPath());
		sreq.getCore().execute(handler, sreq, rsp);
	}
	
	protected void sendError(SolrCore core, SolrQueryRequest req, ServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {
		try {
			SolrQueryResponse solrResp = new SolrQueryResponse();
			if (ex instanceof Exception) {
				solrResp.setException((Exception) ex);
			}
			else {
				solrResp.setException(new RuntimeException(ex));
			}
			if (core == null) {
				core = cores.getCore(""); // default core
			}
			if (req == null) {
				final SolrParams solrParams;
				if (request instanceof HttpServletRequest) {
					// use GET parameters if available:
					solrParams = SolrRequestParsers.parseQueryString(((HttpServletRequest) request).getQueryString());
				}
				else {
					// we have no params at all, use empty ones:
					solrParams = new MapSolrParams(Collections.<String, String> emptyMap());
				}
				req = new SolrQueryRequestBase(core, solrParams) {
				};
			}
			QueryResponseWriter writer = core.getQueryResponseWriter(req);
			writeResponse(solrResp, response, writer, req, Method.GET);
		}
		catch (Throwable t) { // This error really does not matter
			SimpleOrderedMap info = new SimpleOrderedMap();
			int code = ResponseUtils.getErrorInfo(ex, info, log);
			response.sendError(code, info.toString());
		}
	}
	
	// ---------------------------------------------------------------------
	// ---------------------------------------------------------------------
	
	/**
	 * Set the prefix for all paths. This is useful if you want to apply the filter to something other then /*, perhaps because you are merging this filter into a larger web application.
	 * 
	 * For example, if web.xml specifies:
	 * 
	 * <pre class="prettyprint">
	 * {@code
	 * <filter-mapping>
	 *  <filter-name>SolrRequestFilter</filter-name>
	 *  <url-pattern>/xxx/*</url-pattern>
	 * </filter-mapping>}
	 * </pre>
	 * 
	 * Make sure to set the PathPrefix to "/xxx" either with this function or in web.xml.
	 * 
	 * <pre class="prettyprint">
	 * {@code
	 * <init-param>
	 *  <param-name>path-prefix</param-name>
	 *  <param-value>/xxx</param-value>
	 * </init-param>}
	 * </pre>
	 */
	public void setPathPrefix(String pathPrefix) {
		this.pathPrefix = pathPrefix;
	}
	
	public String getPathPrefix() {
		return pathPrefix;
	}
}
